import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
import Alert, { type AlertProps } from '@mui/material/Alert';
import Snackbar, { type SnackbarProps } from '@mui/material/Snackbar';
import { render } from 'rc-util/lib/React/render';

type ISnackbarMethodType = Required<AlertProps>['severity'];

type ISnackbarOptions = Omit<AlertProps, 'children' | 'content' | 'onClose'> &
  Pick<SnackbarProps, 'anchorOrigin' | 'autoHideDuration'> & {
    content?: AlertProps['children'];
    onClose?: VoidFunction;
  };

type IGlobalMessage = {
  instance?: ISnackbarInstance;
  holder?: HTMLDivElement;
};

type ISnackbarMethod = (options: ISnackbarOptions) => MessageType;

type Task =
  | TypeTask
  | {
      type: 'destroy';
      key?: React.Key;
      skipped?: boolean;
    };
interface TypeTask {
  type: ISnackbarMethodType;
  args: Parameters<ISnackbarMethod>;
  resolve: VoidFunction;
  setCloseFn: (closeFn: VoidFunction) => void;
  skipped?: boolean;
}

interface MessageType extends PromiseLike<boolean> {
  (): void;
}

export type ISnackbarInstance = Record<Required<AlertProps>['severity'], ISnackbarMethod> & {
  open: (options: ISnackbarOptions & { type: ISnackbarMethodType }) => MessageType;
  close: () => void;
};

let globalMessage: IGlobalMessage | null = null;

const methods: ISnackbarMethodType[] = ['success', 'info', 'warning', 'error'];

let taskQueue: Task[] = [];

const wrapPromiseFn = (openFn: (resolve: VoidFunction) => VoidFunction) => {
  let closeFn: VoidFunction;

  const closePromise = new Promise<boolean>((resolve) => {
    closeFn = openFn(() => {
      resolve(true);
    });
  });

  const result: any = () => {
    closeFn?.();
  };

  result.then = (filled: VoidFunction, rejected: VoidFunction) => closePromise.then(filled, rejected);
  result.promise = closePromise;

  return result;
};

export const useSnackbar = () => {
  const [open, setOpen] = useState(false);
  const optionsRef = useRef<Omit<ISnackbarOptions, 'content'> & Pick<AlertProps, 'children'>>();
  const { autoHideDuration, anchorOrigin, ...alertProps } = optionsRef.current ?? {};

  const snackbar = useMemo<ISnackbarInstance>(() => {
    const close: ISnackbarInstance['close'] = () => {
      setOpen(false);
      optionsRef.current?.onClose?.();
      setTimeout(() => {
        optionsRef.current = undefined;
      }, 400);
    };
    const open: ISnackbarInstance['open'] = ({ type, ...resetOptions }) => {
      return wrapPromiseFn((resolve) => {
        const { content, ...reset } = resetOptions;
        optionsRef.current = { ...reset, children: content, severity: type };
        setOpen(true);
        return () => {
          close();
          resolve();
        };
      });
    };
    const clone = { open, close } as ISnackbarInstance;
    methods.forEach((type) => {
      clone[type] = (options: ISnackbarOptions) => open({ type, ...options });
    });

    return clone;
  }, [open]);
  return [
    snackbar,
    <Snackbar
      autoHideDuration={autoHideDuration ?? 2000}
      anchorOrigin={anchorOrigin ?? { vertical: 'top', horizontal: 'center' }}
      open={!!open}
      onClose={snackbar.close}
    >
      <Alert variant="filled" sx={{ width: '100%' }} {...alertProps} onClose={snackbar.close} />
    </Snackbar>,
  ];
};

const HolderWrapper = forwardRef<{ instance: ISnackbarInstance }>((_, ref) => {
  const [snackbar, holder] = useSnackbar();
  useImperativeHandle(ref, () => ({ instance: snackbar as ISnackbarInstance }));
  return <>{holder}</>;
});

const flushNotice = () => {
  if (!globalMessage) {
    const holder = document.createElement('div');
    holder.className = 'smap-message-holder';
    document.body.appendChild(holder);
    const newMessage: IGlobalMessage = { holder };
    globalMessage = newMessage;
    render(
      <HolderWrapper
        ref={(node) => {
          const { instance } = node || {};
          Promise.resolve().then(() => {
            if (!newMessage.instance && instance) {
              newMessage.instance = instance;
              flushNotice();
            }
          });
        }}
      />,
      holder,
    );
    return;
  }

  if (!globalMessage.instance) return;

  taskQueue.forEach((task) => {
    const { type, skipped } = task;
    if (!skipped) {
      switch (type) {
        case 'destroy':
          break;
        default: {
          const closeFn = globalMessage!.instance![type](...task.args);
          closeFn?.then(task.resolve);
          task.setCloseFn(closeFn);
        }
      }
    }
  });

  taskQueue = [];
};
const typeOpen = (type: ISnackbarMethodType, args: Parameters<ISnackbarMethod>): MessageType => {
  const result = wrapPromiseFn((resolve) => {
    let closeFn: VoidFunction;

    const task: TypeTask = {
      type,
      args,
      resolve,
      setCloseFn: (fn) => {
        closeFn = fn;
      },
    };

    taskQueue.push(task);

    return () => {
      if (closeFn) {
        closeFn();
      } else {
        task.skipped = true;
      }
    };
  });

  flushNotice();

  return result;
};

export const message = {} as ISnackbarInstance;

methods.forEach((type: ISnackbarMethodType) => {
  message[type] = (...args: Parameters<ISnackbarMethod>) => typeOpen(type, args);
});
